import { Request } from "express";
import { AccessToken, PrismaClient, User } from "@prisma/client";
import { prisma } from "./prisma";

export interface Context {
    readonly prisma: PrismaClient;
    readonly accessToken?: AccessToken;
    readonly user?: User;
    request: Request;
}

function baseContextFactory(): Context {
    return <Context> { prisma };
}

function getTokenForFn(name: string, fn?: (name: string) => string | undefined) {
    if (fn && fn instanceof Function) {
        return fn(name);
    }
}

function removeTokenBearerStr(token?: string): string | undefined {
    const startStr = "Bearer";
    if (token && token.substr(0, startStr.length).toLocaleLowerCase() === startStr.toLocaleLowerCase()) {
        return token.substr(startStr.length);
    }
}

function getToken(request: Request): string | undefined {
    const headerName = "Authorization";
    const one = getTokenForFn(headerName, request.header);
    const two = getTokenForFn(headerName, (name) => {
        if (request.headers && typeof request.headers === "object") {
            for (let key in request.headers) {
                if (key.toLocaleLowerCase() === name.toLocaleLowerCase()) {
                    const value = request.headers[name];
                    if (Array.isArray(value)) {
                        return value.join();
                    }
                    return value;
                }
            }
        }
    });
    
    return removeTokenBearerStr(one || two)?.trim();
}

async function fetchAccessToken(context: Context, token?: string): Promise<Context> {
    if (token) {
        const accessToken = await context.prisma.accessToken.findUnique({
            where: { token },
            rejectOnNotFound: false,
        });
        if (accessToken) {
            return {
                ...context,
                accessToken
            };
        }
    }
    return context;
}

async function fetchUser(context: Context, userId?: string): Promise<Context> {
    if (userId) {
        const user = await context.prisma.user.findUnique({
            where: { id: userId },
            rejectOnNotFound: false,
        });
        if (user) {
            return {
                ...context,
                user
            };
        }
    }
    return context;
}


export async function contextFactory(request: Request): Promise<Context> {
    request.context = baseContextFactory();
    request.context = await fetchAccessToken(request.context, getToken(request));
    request.context = await fetchUser(request.context, request.context.accessToken?.userId);
    request.context.request = request;

    return request.context;
}
